﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;
using xxpl1.HTTP.Parser;
using xxpl1.HTTP.Runtime;
using xxpl1.Log;
using xxpl1.Utils;

namespace xxpl1.GAE.Simple2
{
    public class SimpleHandler2 : IHttpRequestHandler
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof(SimpleHandler2));

        public virtual void Execute(HandlerContext context)
        {
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            if (context.Result == HanlderReult.Ok)
                return;

            if (Settings.Instance.EnableRange
                && "GET".Equals(request.Method, StringComparison.OrdinalIgnoreCase)
                && request.Headers.ContainsKey("range")
                && Settings.Instance.RangeMine.Exists(a => request.RawUrl.EndsWith(a)))
            {
                context.Result = HanlderReult.NextHanler;
                return;
            }

            HttpWebResponse responseFromProxy = null;
            HttpWebRequest requestToProxy = null;
            try
            {
                requestToProxy = GetRequestToProxy(request);

                responseFromProxy = (HttpWebResponse)requestToProxy.GetResponse();

                ProcessHeader(responseFromProxy, response);

                ProcessResponse(responseFromProxy, response);

                logger.InfoFormat("{0}-{1} {2} {3}ms", requestToProxy.Host, request.RawUrl, response.Status, request.ElapsedMs);

                context.Result = HanlderReult.Ok;
            }
            catch (Exception hex)
            {
                context.AddException(hex);
                logger.Debug("Download failed:" + request.RawUrl, hex);
                WebException wex = hex as WebException;
                if (wex != null && wex.Response != null)
                {
                    wex.Response.Close();

                    if ((int)((HttpWebResponse)wex.Response).StatusCode == 509)
                    {
                        context.Result = HanlderReult.NextHanler;
                        return;
                    }
                }
                context.Result = HanlderReult.Failed;
            }
            finally
            {
                if (responseFromProxy != null)
                {
                    responseFromProxy.Close();
                }
                else
                {
                    if (requestToProxy != null)
                        requestToProxy.Abort();
                }
            }
        }

        protected virtual int GetHeaderSize(HttpWebResponse httpWebResponse)
        {
            string[] len = httpWebResponse.GetResponseHeader("X-Length").Split(',');
            return int.Parse(len[0]);
        }

        protected virtual int GetContentSize(HttpWebResponse httpWebResponse)
        {
            string[] len = httpWebResponse.GetResponseHeader("X-Length").Split(',');
            return int.Parse(len[1]);
        }

        protected virtual byte GetKey(HttpWebResponse httpWebResponse)
        {
            int key = int.Parse(httpWebResponse.GetResponseHeader("X-Key"));
            return (byte)(key < 0 ? key + 256 : key);
        }

        protected virtual byte[] ReadBytesFromStream(Stream stream, int len)
        {
            byte[] temp = new byte[len];
            int count = 0;

            do
            {
                count += stream.Read(temp, count, len - count);
            } while (count < len);

            return temp;
        }

        protected virtual void ProcessHeader(HttpWebResponse responseFromProxy, HttpResponse response)
        {
            int headerSize = GetHeaderSize(responseFromProxy);

            byte[] headerBytes = ReadBytesFromStream(responseFromProxy.GetResponseStream(), headerSize);

            var ep = new XorEncryptor { Key = GetKey(responseFromProxy) };
            ep.Encrypt(headerBytes);

            string[] headers = Encoding.UTF8.GetString(headerBytes, 0, headerSize).Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            response.Status = int.Parse(headers[0]);
            response.ContentLength = GetContentSize(responseFromProxy);

            for (int i = 1; i < headers.Length; i++)
            {
                int pos = headers[i].IndexOf(':');
                if (pos < 0) continue;

                string header = headers[i].Substring(0, pos).Trim();
                string value = headers[i].Substring(pos + 1).Trim();

                if (header.Equals("set-cookie", StringComparison.OrdinalIgnoreCase))
                {
                    ProcessCooike(response, value);
                }
                else if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.Parse(value) != GetContentSize(responseFromProxy))
                    {
                        logger.Error("Wrong Data Size");
                    }
                }
                else response.AddHeader(header, value);
            }
        }

        protected virtual void ProcessCooike(HttpResponse response, string setCookies)
        {
            if (string.IsNullOrEmpty(setCookies)) return;

            string[] cookies = setCookies.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);

            List<string> cookieHeader = new List<string>();

            foreach (string cookie in cookies)
            {
                int x = cookie.IndexOf("=");
                int y = cookie.IndexOf(";");
                if (x < 0 || (y > -1 && x > y))
                {
                    cookieHeader[cookieHeader.Count - 1] = string.Format("{0}, {1}", cookieHeader[cookieHeader.Count - 1], cookie);
                }
                else
                {
                    cookieHeader.Add(cookie);
                }
            }

            foreach (string match in cookieHeader)
            {
                response.AddCookie(match);
            }
        }

        protected virtual void ProcessResponse(HttpWebResponse responseFromProxy, HttpResponse response)
        {
            int length = GetContentSize(responseFromProxy);

            int count = 0;
            var result = new byte[2048];

            using (Stream streamFromProxy = responseFromProxy.GetResponseStream())
            {
                var ep = new XorEncryptor { Key = GetKey(responseFromProxy) };

                while (count < length)
                {
                    int readed = streamFromProxy.Read(result, 0, length - count > result.Length ? result.Length : length - count);

                    if (readed == 0 || readed < 0)
                    {
                        break;
                    }

                    count += readed;
                    ep.Encrypt(result, 0, readed);
                    response.Write(result, 0, readed);
                }
                streamFromProxy.Close();
            }
        }

        protected virtual HttpWebRequest GetRequestToProxy(HttpRequest request)
        {
            var requestToProxy = (HttpWebRequest)WebRequest.Create("https://" + DnsHelper.GetHost(request.Host) + "/xpp3");
            requestToProxy.Host = DnsHelper.GetGaeHost(request.Host);
            requestToProxy.Method = "POST";
            requestToProxy.AllowAutoRedirect = false;
            requestToProxy.Proxy = EmptyProxy.Instance;
            if (Settings.Instance.ZipHeader)
                requestToProxy.Headers.Add("x-zip", "1");

            using (Stream requestStream = requestToProxy.GetRequestStream())
            {
                BlockInputStream gZipStream = Settings.Instance.ZipHeader ? new BlockInputStream(new GZipStream(requestStream, CompressionMode.Compress)) : new BlockInputStream(requestStream);
                StringBuilder sb = new StringBuilder();
                foreach (KeyValuePair<string, string> keyValuePair in request.Headers.Where(keyValuePair => Guard.IsSafeHeader(keyValuePair.Key)))
                {
                    sb.AppendFormat("{0}:{1}\r\n", keyValuePair.Key, keyValuePair.Value);
                }
                gZipStream.WriteBlock(sb.ToString());
                gZipStream.WriteBlock(request.Method);
                gZipStream.WriteBlock(request.RawUrl);
                gZipStream.WriteBlock(request.RequestStream.ToArray());
                gZipStream.Close();
                requestStream.Close();
                logger.Debug("OUT: " + gZipStream.Length);
            }
            return requestToProxy;
        }
    }
}